` ); printWindow.document.close(); const token = sessionStorage.getItem( "pillar_token" ); google.script.run .withSuccessHandler( function(result) { if ( !result || !result.success ) { try { printWindow.close(); } catch (e) {} if ( result && result.message === "UNAUTHORIZED" ) { handleExpiredSession(); return; } showToast( t("quotationDetailsError"), "error" ); return; } const html = buildQuotationPrintHtml( result ); printWindow.document.open(); printWindow.document.write(html); printWindow.document.close(); } ) .withFailureHandler( function(error) { console.error( "Quotation print error:", error ); try { printWindow.close(); } catch (e) {} showToast( t("quotationDetailsError"), "error" ); } ) .getQuotationPrintData( token, quotationId ); } function buildQuotationPrintHtml( data ) { data = data || {}; const company = data.company || {}; const customer = data.customer || {}; const quotation = data.quotation || {}; const items = data.items || []; const isArabic = currentLanguage === "ar"; const direction = isArabic ? "rtl" : "ltr"; const textAlign = isArabic ? "right" : "left"; const oppositeAlign = isArabic ? "left" : "right"; const companyName = company.name || "PILLAR"; const companyLogo = String( company.logoDataUrl || "" ); const companyStamp = String( company.stampDataUrl || "" ); const paymentTerms = String( company.paymentTerms || "" ); const termsConditions = String( company.termsConditions || "" ); const authorizedSignatory = String( company.authorizedSignatory || "" ); const signatoryTitle = String( company.signatoryTitle || "" ); const companyAddressParts = [ company.address, company.country ].filter(Boolean); const customerLocation = [ customer.address, customer.city, customer.country ].filter(Boolean); let itemRows = ""; items.forEach( function(item, index) { itemRows += ` ${index + 1} ${escapeHtml( item.description || "" )} ${ item.notes ? `
${escapeHtml(item.notes)}
` : "" } ${escapeHtml( String( item.quantity == null ? "" : item.quantity ) )} ${escapeHtml( item.unit || "" )} ${escapeHtml( formatQuotationMoney( item.unitPrice || 0 ) )} ${escapeHtml( formatQuotationMoney( item.total || 0 ) )} `; } ); const companyContactLines = [ company.phone ? `${t("phone")}: ${company.phone}` : "", company.email ? `${t("email")}: ${company.email}` : "", company.website || "", company.taxNumber ? `${t("taxNumber")}: ${company.taxNumber}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const customerContactLines = [ customer.contactPerson ? `${t("contactPerson")}: ${customer.contactPerson}` : "", customer.phone ? `${t("phone")}: ${customer.phone}` : "", customer.email ? `${t("email")}: ${customer.email}` : "", customer.taxNumber ? `${t("taxNumber")}: ${customer.taxNumber}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const printTitle = quotation.number ? `${t("quotationTitle")} ${quotation.number}` : t("quotationTitle"); return ` ${escapeHtml(printTitle)}
${ companyLogo ? `` : "" }
${escapeHtml(companyName)}
${ companyAddressParts.length ? `
${escapeHtml(companyAddressParts.join(", "))}
` : "" }
${companyContactLines}

${escapeHtml(t("quotationTitle"))}

${escapeHtml(quotation.number || "")}
${escapeHtml(t("status"))}: ${escapeHtml(quotationStatusText(quotation.status || "Draft"))}
${escapeHtml(t("customer"))}
${escapeHtml( customer.companyName || quotation.customerId || "" )}
${ customerLocation.length ? `
${escapeHtml(customerLocation.join(", "))}
` : "" }
${customerContactLines}
${escapeHtml(t("quotationDate"))} ${escapeHtml(quotation.quotationDate || "")}
${escapeHtml(t("validUntil"))} ${escapeHtml(quotation.validUntil || "")}
${escapeHtml(t("rfqReference"))} ${escapeHtml(data.rfqNumber || "")}
${escapeHtml(t("currency"))} ${escapeHtml(quotation.currency || "")}
${itemRows}
${escapeHtml(t("itemNo"))} ${escapeHtml(t("descriptionField"))} ${escapeHtml(t("quantity"))} ${escapeHtml(t("unit"))} ${escapeHtml(t("unitPrice"))} ${escapeHtml(t("lineTotal"))}
${escapeHtml(t("notes"))}
${escapeHtml(quotation.notes || "")}
${escapeHtml(t("subtotal"))} ${escapeHtml( formatQuotationMoney( quotation.subtotal || 0 ) )} ${escapeHtml(quotation.currency || "")}
${escapeHtml(t("tax"))} ${escapeHtml( formatQuotationMoney( quotation.tax || 0 ) )} ${escapeHtml(quotation.currency || "")}
${escapeHtml(t("total"))} ${escapeHtml( formatQuotationMoney( quotation.total || 0 ) )} ${escapeHtml(quotation.currency || "")}
${ paymentTerms || termsConditions || authorizedSignatory || signatoryTitle || companyStamp ? `
${ paymentTerms ? `
${escapeHtml(t("paymentTerms"))}
${escapeHtml(paymentTerms)}
` : "" } ${ termsConditions ? `
${escapeHtml(t("termsConditions"))}
${escapeHtml(termsConditions)}
` : "" }
${escapeHtml(t("authorizedSignature"))}
${ companyStamp ? `${escapeAttribute(t(` : "" }
${ authorizedSignatory ? `
${escapeHtml(authorizedSignatory)}
` : "" } ${ signatoryTitle ? `
${escapeHtml(signatoryTitle)}
` : "" }
` : "" }
`; } function closeQuotationModal() { const overlay = document.getElementById( "quotationModalOverlay" ); if (overlay) { overlay.remove(); } } // ====================================================== // SESSION EXPIRED // ====================================================== // ORDERS PAGE // ====================================================== function renderOrdersPage() { const content = document.getElementById("mainContent"); content.innerHTML = `
${hasUiPermission("orders", "create") ? `` : ""}
${t("loading")}
`; loadOrders(); } function loadOrders() { const token = sessionStorage.getItem("pillar_token"); const area = document.getElementById("ordersTableArea"); google.script.run .withSuccessHandler(function(result) { if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") { handleExpiredSession(); return; } if (area) { area.innerHTML = `
${t("orderLoadError")}
`; } return; } ordersData = result.orders || []; drawOrdersTable(ordersData); }) .withFailureHandler(function(error) { console.error("Orders load error:", error); if (area) { area.innerHTML = `
${t("orderLoadError")}
`; } }) .getOrders(token); } function drawOrdersTable(data) { const area = document.getElementById("ordersTableArea"); if (!area) return; if (!data || !data.length) { area.innerHTML = `
${t("noOrders")}
`; return; } let rows = ""; data.forEach(function(order) { rows += ` ${escapeHtml(order.orderNumber || "")} ${escapeHtml(order.quotationNumber || "")} ${escapeHtml(order.customerName || order.customer || "")} ${escapeHtml(order.orderDate || "")} ${escapeHtml(order.currency || "")} ${formatQuotationMoney(order.total || 0)} ${orderStatusText(order.status)}
${hasUiPermission("orders", "edit") ? `` : ""} ${hasUiPermission("orders", "edit") ? `` : ""}
`; }); area.innerHTML = ` ${rows}
${t("orderNumber")} ${t("quotationNumber")} ${t("customer")} ${t("orderDate")} ${t("currency")} ${t("total")} ${t("status")} ${t("actions")}
`; } function filterOrders() { const q = (document.getElementById("orderSearch")?.value || "").toLowerCase().trim(); const filtered = ordersData.filter(function(order) { return [order.orderNumber, order.quotationNumber, order.customerName, order.customer, order.status, order.currency] .join(" ").toLowerCase().includes(q); }); drawOrdersTable(filtered); } function orderStatusText(status) { const map = { Pending: "pending", Confirmed: "confirmed", Processing: "processing", Shipped: "shipped", Delivered: "delivered", Completed: "completed", Cancelled: "cancelled" }; return t(map[status] || "pending"); } function orderStatusClass(status) { return ["Cancelled"].includes(status) ? "status-inactive" : "status-active"; } function orderStatusOptions(selected) { const values = ["Pending", "Confirmed", "Processing", "Shipped", "Delivered", "Cancelled"]; return values.map(function(value) { return ``; }).join(""); } function openOrderModal() { const token = sessionStorage.getItem("pillar_token"); google.script.run .withSuccessHandler(function(result) { if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") return handleExpiredSession(); showToast(t("orderLoadError"), "error"); return; } orderQuotationsData = result.quotations || []; showOrderModal(null, null); }) .withFailureHandler(function(error) { console.error(error); showToast(t("orderLoadError"), "error"); }) .getOrderQuotations(token); } function editOrder(orderId) { const token = sessionStorage.getItem("pillar_token"); let detailsResult = null; let quotationsResult = null; let completed = 0; function finish() { completed++; if (completed < 2) return; if (!detailsResult || !detailsResult.success) { if (detailsResult && detailsResult.message === "UNAUTHORIZED") return handleExpiredSession(); showToast(t("orderDetailsError"), "error"); return; } orderQuotationsData = quotationsResult && quotationsResult.success ? (quotationsResult.quotations || []) : []; showOrderModal(detailsResult.order || detailsResult, detailsResult.items || []); } google.script.run.withSuccessHandler(function(r){ detailsResult=r; finish(); }) .withFailureHandler(function(e){ console.error(e); detailsResult={success:false}; finish(); }) .getOrderDetails(token, orderId); google.script.run.withSuccessHandler(function(r){ quotationsResult=r; finish(); }) .withFailureHandler(function(e){ console.error(e); quotationsResult={success:false}; finish(); }) .getOrderQuotations(token); } function showOrderModal(order, items) { const isEdit = !!(order && order.id); const selectedQuotation = order ? (order.quotationId || "") : ""; const today = new Date().toISOString().slice(0,10); const quotationOptions = orderQuotationsData.map(function(q) { const id = q.id || q.quotationId || ""; const number = q.quotationNumber || id; const customer = q.customerName || q.customer || ""; return ``; }).join(""); const overlay = document.createElement("div"); overlay.id = "orderModalOverlay"; overlay.className = "modal-overlay"; overlay.innerHTML = `

${isEdit ? t("editOrder") : t("newOrder")}

${t("items")}

${isEdit ? "" : t("selectQuotation")}
`; document.body.appendChild(overlay); if (items && items.length) drawOrderItems(items); else if (isEdit && order.items) drawOrderItems(order.items); } function orderQuotationChanged() { const quotationId = document.getElementById("orderQuotation").value; if (!quotationId) { document.getElementById("orderCustomer").value = ""; document.getElementById("orderCurrency").value = ""; document.getElementById("orderTotal").value = ""; document.getElementById("orderItemsArea").innerHTML = `
${t("selectQuotation")}
`; return; } const token = sessionStorage.getItem("pillar_token"); google.script.run .withSuccessHandler(function(result) { if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") return handleExpiredSession(); showToast(t("quotationDetailsError"), "error"); return; } const q = result.quotation || result; document.getElementById("orderCustomer").value = q.customerName || q.customer || ""; document.getElementById("orderCurrency").value = q.currency || ""; document.getElementById("orderTotal").value = q.total || 0; drawOrderItems(result.items || q.items || []); }) .withFailureHandler(function(error) { console.error(error); showToast(t("quotationDetailsError"), "error"); }) .getQuotationForOrder(token, quotationId); } function drawOrderItems(items) { const area = document.getElementById("orderItemsArea"); if (!area) return; if (!items || !items.length) { area.innerHTML = `
${t("quotationItemsRequired")}
`; return; } let rows = ""; items.forEach(function(item, index) { rows += ` ${index + 1} ${escapeHtml(item.description || "")} ${escapeHtml(String(item.quantity || ""))} ${escapeHtml(item.unit || "")} ${formatQuotationMoney(item.unitPrice || 0)} ${formatQuotationMoney(item.lineTotal || item.total || 0)} `; }); area.innerHTML = `${rows}
${t("itemNo")}${t("descriptionField")}${t("quantity")} ${t("unit")}${t("unitPrice")}${t("lineTotal")}
`; } function saveOrder(orderId) { const quotationEl = document.getElementById("orderQuotation"); const quotationId = quotationEl ? quotationEl.value : ""; if (!quotationId) { showToast(t("quotationRequired"), "error"); return; } const order = { id: orderId || "", quotationId: quotationId, orderDate: document.getElementById("orderDate").value, status: document.getElementById("orderStatus").value, notes: document.getElementById("orderNotes").value.trim() }; const token = sessionStorage.getItem("pillar_token"); const button = document.getElementById("saveOrderButton"); button.disabled = true; const runner = google.script.run .withSuccessHandler(function(result) { button.disabled = false; if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") return handleExpiredSession(); showToast((result && result.message) || t("systemError"), "error"); return; } closeOrderModal(); loadOrders(); loadDashboardStats(); showToast(orderId ? t("orderUpdated") : t("orderAdded"), "success"); }) .withFailureHandler(function(error) { button.disabled = false; console.error(error); showToast(t("systemError"), "error"); }); if (orderId) runner.updateOrder(token, order); else runner.addOrder(token, order); } // ====================================================== // INVOICE PRINT / PDF // ====================================================== function printInvoice( orderId ) { if (!orderId) { showToast( t("invoiceCreateError"), "error" ); return; } const printWindow = window.open( "", "_blank" ); if (!printWindow) { showToast( currentLanguage === "ar" ? "يرجى السماح بالنوافذ المنبثقة لفتح معاينة الفاتورة." : "Please allow pop-ups to open the invoice preview.", "error" ); return; } printWindow.document.open(); printWindow.document.write( ` ${escapeHtml(t("loading"))} ${escapeHtml(t("loading"))} ` ); printWindow.document.close(); const token = sessionStorage.getItem( "pillar_token" ); google.script.run .withSuccessHandler( function(result) { if ( !result || !result.success ) { try { printWindow.close(); } catch (e) {} if ( result && result.message === "UNAUTHORIZED" ) { handleExpiredSession(); return; } showToast( t("invoiceCreateError"), "error" ); return; } const html = buildInvoicePrintHtml( result ); printWindow.document.open(); printWindow.document.write(html); printWindow.document.close(); } ) .withFailureHandler( function(error) { console.error( "Invoice print error:", error ); try { printWindow.close(); } catch (e) {} showToast( t("invoiceCreateError"), "error" ); } ) .getInvoicePrintData( token, orderId ); } function buildInvoicePrintHtml( data ) { data = data || {}; const company = data.company || {}; const customer = data.customer || {}; const invoice = data.invoice || {}; const order = data.order || {}; const items = data.items || []; const isArabic = currentLanguage === "ar"; const direction = isArabic ? "rtl" : "ltr"; const textAlign = isArabic ? "right" : "left"; const oppositeAlign = isArabic ? "left" : "right"; const companyName = company.name || "PILLAR"; const companyLogo = String( company.logoDataUrl || "" ); const companyStamp = String( company.stampDataUrl || "" ); const paymentTerms = String( company.paymentTerms || "" ); const termsConditions = String( company.termsConditions || "" ); const authorizedSignatory = String( company.authorizedSignatory || "" ); const signatoryTitle = String( company.signatoryTitle || "" ); const companyAddressParts = [ company.address, company.country ].filter(Boolean); const customerLocation = [ customer.address, customer.city, customer.country ].filter(Boolean); let itemRows = ""; items.forEach( function(item, index) { itemRows += ` ${escapeHtml( String( item.itemNo || index + 1 ) )} ${escapeHtml( item.description || "" )} ${ item.notes ? `
${escapeHtml(item.notes)}
` : "" } ${escapeHtml( String( item.quantity == null ? "" : item.quantity ) )} ${escapeHtml( item.unit || "" )} ${escapeHtml( formatQuotationMoney( item.unitPrice || 0 ) )} ${escapeHtml( formatQuotationMoney( item.total || 0 ) )} `; } ); const companyContactLines = [ company.phone ? `${t("phone")}: ${company.phone}` : "", company.email ? `${t("email")}: ${company.email}` : "", company.website || "", company.taxNumber ? `${t("taxNumber")}: ${company.taxNumber}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const customerContactLines = [ customer.contactPerson ? `${t("contactPerson")}: ${customer.contactPerson}` : "", customer.phone ? `${t("phone")}: ${customer.phone}` : "", customer.email ? `${t("email")}: ${customer.email}` : "", customer.taxNumber ? `${t("taxNumber")}: ${customer.taxNumber}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const printTitle = invoice.number ? `${t("invoiceTitle")} ${invoice.number}` : t("invoiceTitle"); return ` ${escapeHtml(printTitle)}
${ companyLogo ? `` : "" }
${escapeHtml(companyName)}
${ companyAddressParts.length ? `
${escapeHtml(companyAddressParts.join(", "))}
` : "" }
${companyContactLines}

${escapeHtml(t("invoiceTitle"))}

${escapeHtml(invoice.number || "")}
${escapeHtml(t("status"))}: ${escapeHtml( invoiceStatusText( invoice.displayStatus || invoice.status || "Issued" ) )}
${escapeHtml(t("customer"))}
${escapeHtml( customer.companyName || invoice.customerId || "" )}
${ customerLocation.length ? `
${escapeHtml(customerLocation.join(", "))}
` : "" }
${customerContactLines}
${escapeHtml(t("invoiceDate"))} ${escapeHtml(invoice.invoiceDate || "")}
${escapeHtml(t("orderReference"))} ${escapeHtml(order.number || "")}
${escapeHtml(t("quotationReference"))} ${escapeHtml(data.quotationNumber || "")}
${escapeHtml(t("currency"))} ${escapeHtml(invoice.currency || order.currency || "")}
${itemRows}
${escapeHtml(t("itemNo"))} ${escapeHtml(t("descriptionField"))} ${escapeHtml(t("quantity"))} ${escapeHtml(t("unit"))} ${escapeHtml(t("unitPrice"))} ${escapeHtml(t("lineTotal"))}
${escapeHtml(t("notes"))}
${escapeHtml(invoice.notes || order.notes || "")}
${escapeHtml(t("total"))} ${escapeHtml( formatQuotationMoney( invoice.total || order.total || 0 ) )} ${escapeHtml(invoice.currency || order.currency || "")}
${ paymentTerms || termsConditions || authorizedSignatory || signatoryTitle || companyStamp ? `
${ paymentTerms ? `
${escapeHtml(t("paymentTerms"))}
${escapeHtml(paymentTerms)}
` : "" } ${ termsConditions ? `
${escapeHtml(t("termsConditions"))}
${escapeHtml(termsConditions)}
` : "" }
${escapeHtml(t("authorizedSignature"))}
${ companyStamp ? `${escapeAttribute(t(` : "" }
${ authorizedSignatory ? `
${escapeHtml(authorizedSignatory)}
` : "" } ${ signatoryTitle ? `
${escapeHtml(signatoryTitle)}
` : "" }
` : "" }
`; } // ====================================================== // ORDER PRINT / PDF // ====================================================== function printOrder( orderId ) { if (!orderId) { showToast( t("orderDetailsError"), "error" ); return; } const printWindow = window.open( "", "_blank" ); if (!printWindow) { showToast( currentLanguage === "ar" ? "يرجى السماح بالنوافذ المنبثقة لفتح معاينة الطباعة." : "Please allow pop-ups to open the print preview.", "error" ); return; } printWindow.document.open(); printWindow.document.write( ` ${escapeHtml(t("loading"))} ${escapeHtml(t("loading"))} ` ); printWindow.document.close(); const token = sessionStorage.getItem( "pillar_token" ); google.script.run .withSuccessHandler( function(result) { if ( !result || !result.success ) { try { printWindow.close(); } catch (e) {} if ( result && result.message === "UNAUTHORIZED" ) { handleExpiredSession(); return; } showToast( t("orderDetailsError"), "error" ); return; } const html = buildOrderPrintHtml( result ); printWindow.document.open(); printWindow.document.write(html); printWindow.document.close(); } ) .withFailureHandler( function(error) { console.error( "Order print error:", error ); try { printWindow.close(); } catch (e) {} showToast( t("orderDetailsError"), "error" ); } ) .getOrderPrintData( token, orderId ); } function buildOrderPrintHtml( data ) { data = data || {}; const company = data.company || {}; const customer = data.customer || {}; const order = data.order || {}; const items = data.items || []; const isArabic = currentLanguage === "ar"; const direction = isArabic ? "rtl" : "ltr"; const textAlign = isArabic ? "right" : "left"; const oppositeAlign = isArabic ? "left" : "right"; const companyName = company.name || "PILLAR"; const companyLogo = String( company.logoDataUrl || "" ); const companyStamp = String( company.stampDataUrl || "" ); const paymentTerms = String( company.paymentTerms || "" ); const termsConditions = String( company.termsConditions || "" ); const authorizedSignatory = String( company.authorizedSignatory || "" ); const signatoryTitle = String( company.signatoryTitle || "" ); const companyAddressParts = [ company.address, company.country ].filter(Boolean); const customerLocation = [ customer.address, customer.city, customer.country ].filter(Boolean); let itemRows = ""; items.forEach( function(item, index) { itemRows += ` ${escapeHtml( String( item.itemNo || index + 1 ) )} ${escapeHtml( item.description || "" )} ${ item.notes ? `
${escapeHtml(item.notes)}
` : "" } ${escapeHtml( String( item.quantity == null ? "" : item.quantity ) )} ${escapeHtml( item.unit || "" )} ${escapeHtml( formatQuotationMoney( item.unitPrice || 0 ) )} ${escapeHtml( formatQuotationMoney( item.total || 0 ) )} `; } ); const companyContactLines = [ company.phone ? `${t("phone")}: ${company.phone}` : "", company.email ? `${t("email")}: ${company.email}` : "", company.website || "", company.taxNumber ? `${t("taxNumber")}: ${company.taxNumber}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const customerContactLines = [ customer.contactPerson ? `${t("contactPerson")}: ${customer.contactPerson}` : "", customer.phone ? `${t("phone")}: ${customer.phone}` : "", customer.email ? `${t("email")}: ${customer.email}` : "", customer.taxNumber ? `${t("taxNumber")}: ${customer.taxNumber}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const printTitle = order.number ? `${t("orderTitle")} ${order.number}` : t("orderTitle"); return ` ${escapeHtml(printTitle)}
${ companyLogo ? `` : "" }
${escapeHtml(companyName)}
${ companyAddressParts.length ? `
${escapeHtml(companyAddressParts.join(", "))}
` : "" }
${companyContactLines}

${escapeHtml(t("orderTitle"))}

${escapeHtml(order.number || "")}
${escapeHtml(t("status"))}: ${escapeHtml(orderStatusText(order.status || "Pending"))}
${escapeHtml(t("customer"))}
${escapeHtml( customer.companyName || order.customerId || "" )}
${ customerLocation.length ? `
${escapeHtml(customerLocation.join(", "))}
` : "" }
${customerContactLines}
${escapeHtml(t("orderDate"))} ${escapeHtml(order.orderDate || "")}
${escapeHtml(t("quotationReference"))} ${escapeHtml(data.quotationNumber || "")}
${escapeHtml(t("currency"))} ${escapeHtml(order.currency || "")}
${itemRows}
${escapeHtml(t("itemNo"))} ${escapeHtml(t("descriptionField"))} ${escapeHtml(t("quantity"))} ${escapeHtml(t("unit"))} ${escapeHtml(t("unitPrice"))} ${escapeHtml(t("lineTotal"))}
${escapeHtml(t("notes"))}
${escapeHtml(order.notes || "")}
${escapeHtml(t("orderTotal"))} ${escapeHtml( formatQuotationMoney( order.total || 0 ) )} ${escapeHtml(order.currency || "")}
${ paymentTerms || termsConditions || authorizedSignatory || signatoryTitle || companyStamp ? `
${ paymentTerms ? `
${escapeHtml(t("paymentTerms"))}
${escapeHtml(paymentTerms)}
` : "" } ${ termsConditions ? `
${escapeHtml(t("termsConditions"))}
${escapeHtml(termsConditions)}
` : "" }
${escapeHtml(t("authorizedSignature"))}
${ companyStamp ? `${escapeAttribute(t(` : "" }
${ authorizedSignatory ? `
${escapeHtml(authorizedSignatory)}
` : "" } ${ signatoryTitle ? `
${escapeHtml(signatoryTitle)}
` : "" }
` : "" }
`; } function closeOrderModal() { const modal = document.getElementById("orderModalOverlay"); if (modal) modal.remove(); } // ====================================================== // INVOICES PAGE // ====================================================== function renderInvoicesPage() { const content = document.getElementById( "mainContent" ); content.innerHTML = `
${t("loading")}
`; loadInvoices(); } function loadInvoices() { const token = sessionStorage.getItem( "pillar_token" ); const area = document.getElementById( "invoicesTableArea" ); google.script.run .withSuccessHandler( function(result) { if ( !result || !result.success ) { if ( result && result.message === "UNAUTHORIZED" ) { handleExpiredSession(); return; } if (area) { area.innerHTML = `
${t("invoiceLoadError")}
`; } return; } invoicesData = result.invoices || []; drawInvoicesTable( invoicesData ); } ) .withFailureHandler( function(error) { console.error( "Invoices load error:", error ); if (area) { area.innerHTML = `
${t("invoiceLoadError")}
`; } } ) .getInvoices( token ); } function drawInvoicesTable( rows ) { const area = document.getElementById( "invoicesTableArea" ); if (!area) { return; } if ( !rows || !rows.length ) { area.innerHTML = `
${t("noInvoices")}
`; return; } let html = ` `; rows.forEach( function(invoice) { html += ` `; } ); html += `
${t("invoiceNumber")} ${t("orderNumber")} ${t("customer")} ${t("invoiceDate")} ${t("currency")} ${t("total")} ${t("paid")} ${t("balance")} ${t("status")} ${t("actions")}
${escapeHtml( invoice.number || "" )} ${escapeHtml( invoice.orderNumber || "" )} ${escapeHtml( invoice.customerName || "" )} ${escapeHtml( invoice.invoiceDate || "" )} ${escapeHtml( invoice.currency || "" )} ${formatQuotationMoney( invoice.total || 0 )} ${formatQuotationMoney( invoice.paid || 0 )} ${formatQuotationMoney( invoice.balance || 0 )} ${invoiceStatusText(invoice.status)}
`; area.innerHTML = html; } function filterInvoices() { const query = ( document.getElementById( "invoiceSearch" )?.value || "" ) .toLowerCase() .trim(); const filtered = invoicesData.filter( function(invoice) { return [ invoice.number, invoice.orderNumber, invoice.customerName, invoice.invoiceDate, invoice.currency, invoice.status ] .join(" ") .toLowerCase() .includes(query); } ); drawInvoicesTable( filtered ); } function invoiceStatusText( status ) { const map = { "Issued": "invoiceIssued", "Partially Paid": "partiallyPaid", "Paid": "paid", "Cancelled": "cancelled" }; return t( map[status] || "invoiceIssued" ); } function invoiceStatusClass( status ) { const map = { "Issued": "invoice-status-issued", "Partially Paid": "invoice-status-partial", "Paid": "invoice-status-paid", "Cancelled": "invoice-status-cancelled" }; return ( map[status] || "invoice-status-issued" ); } function printExistingInvoice( invoiceId ) { if (!invoiceId) { showToast( t("invoiceCreateError"), "error" ); return; } const printWindow = window.open( "", "_blank" ); if (!printWindow) { showToast( currentLanguage === "ar" ? "يرجى السماح بالنوافذ المنبثقة لفتح معاينة الفاتورة." : "Please allow pop-ups to open the invoice preview.", "error" ); return; } printWindow.document.open(); printWindow.document.write( ` ${escapeHtml(t("loading"))} ${escapeHtml(t("loading"))} ` ); printWindow.document.close(); const token = sessionStorage.getItem( "pillar_token" ); google.script.run .withSuccessHandler( function(result) { if ( !result || !result.success ) { try { printWindow.close(); } catch (e) {} if ( result && result.message === "UNAUTHORIZED" ) { handleExpiredSession(); return; } showToast( t("invoiceCreateError"), "error" ); return; } const html = buildInvoicePrintHtml( result ); printWindow.document.open(); printWindow.document.write( html ); printWindow.document.close(); } ) .withFailureHandler( function(error) { console.error( "Existing invoice print error:", error ); try { printWindow.close(); } catch (e) {} showToast( t("invoiceCreateError"), "error" ); } ) .getExistingInvoicePrintData( token, invoiceId ); } // ====================================================== // RECEIPTS PAGE // ====================================================== function renderReceiptsPage() { const content = document.getElementById( "mainContent" ); content.innerHTML = `
${t("loading")}
`; loadReceipts(); } function loadReceipts() { const token = sessionStorage.getItem( "pillar_token" ); const area = document.getElementById( "receiptsTableArea" ); google.script.run .withSuccessHandler( function(result) { if ( !result || !result.success ) { if ( result && result.message === "UNAUTHORIZED" ) { handleExpiredSession(); return; } if (area) { area.innerHTML = `
${t("receiptsLoadError")}
`; } return; } receiptsData = result.receipts || []; drawReceiptsTable( receiptsData ); } ) .withFailureHandler( function(error) { console.error( "Receipts load error:", error ); if (area) { area.innerHTML = `
${t("receiptsLoadError")}
`; } } ) .getReceipts( token ); } function drawReceiptsTable( rows ) { const area = document.getElementById( "receiptsTableArea" ); if (!area) { return; } if ( !rows || !rows.length ) { area.innerHTML = `
${t("noReceipts")}
`; return; } let html = ` `; rows.forEach( function(receipt) { html += ` `; } ); html += `
${t("receiptNumber")} ${t("paymentId")} ${t("customer")} ${t("paymentDate")} ${t("amount")} ${t("currency")} ${t("orderNumber")} ${t("invoiceNumber")} ${t("status")} ${t("actions")}
${escapeHtml( receipt.number || "" )} ${escapeHtml( receipt.paymentId || "" )} ${escapeHtml( receipt.customerName || "" )} ${escapeHtml( receipt.receiptDate || "" )} ${formatQuotationMoney( receipt.amount || 0 )} ${escapeHtml( receipt.currency || "" )} ${escapeHtml( receipt.orderNumber || "" )} ${escapeHtml( receipt.invoiceNumber || "—" )} ${escapeHtml( receiptStatusText( receipt.status || "Issued" ) )}
`; area.innerHTML = html; } function filterReceipts() { const query = ( document.getElementById( "receiptSearch" )?.value || "" ) .toLowerCase() .trim(); const filtered = receiptsData.filter( function(receipt) { return [ receipt.number, receipt.paymentId, receipt.customerName, receipt.receiptDate, receipt.currency, receipt.orderNumber, receipt.invoiceNumber, receipt.status ] .join(" ") .toLowerCase() .includes(query); } ); drawReceiptsTable( filtered ); } function receiptStatusText( status ) { const map = { "Issued": "invoiceIssued", "Cancelled": "cancelled" }; const key = map[status]; return key ? t(key) : String(status || ""); } // ====================================================== // ACCOUNTS / PAYMENTS PAGE // ====================================================== function renderAccountsPage() { const content = document.getElementById("mainContent"); content.innerHTML = `
${hasUiPermission("accounts", "create") ? `` : ""}
${t("loading")}

${t("paymentHistory")}

${t("loading")}
`; loadAccounts(); } function loadAccounts() { const token = sessionStorage.getItem("pillar_token"); let a = null, p = null, done = 0; function finish() { done++; if (done < 2) return; if (!a || !a.success) { if (a && a.message === "UNAUTHORIZED") return handleExpiredSession(); showToast(t("accountsLoadError"), "error"); return; } accountOrdersData = a.orders || []; paymentsData = p && p.success ? (p.payments || []) : []; drawAccountOrdersTable(accountOrdersData); drawPaymentsTable(paymentsData); } google.script.run.withSuccessHandler(function(r){a=r;finish();}) .withFailureHandler(function(e){console.error(e);a={success:false};finish();}) .getAccountOrders(token); google.script.run.withSuccessHandler(function(r){p=r;finish();}) .withFailureHandler(function(e){console.error(e);p={success:false};finish();}) .getPayments(token); } function drawAccountOrdersTable(rows) { const area = document.getElementById("accountsTableArea"); if (!area) return; if (!rows || !rows.length) { area.innerHTML = `
${t("noAccountOrders")}
`; return; } let html = ``; rows.forEach(function(o){ html += ``; }); area.innerHTML = html + `
${t("orderNumber")}${t("customer")}${t("currency")} ${t("total")}${t("paid")}${t("balance")} ${t("status")}${t("actions")}
${escapeHtml(o.number || "")}${escapeHtml(o.customerName || "")} ${escapeHtml(o.currency || "")}${formatQuotationMoney(o.total || 0)} ${formatQuotationMoney(o.paid || 0)}${formatQuotationMoney(o.balance || 0)} ${orderStatusText(o.status)} ${hasUiPermission("accounts", "create") && Number(o.balance || 0) > 0 ? `` : "—"}
`; } function drawPaymentsTable(rows) { const area = document.getElementById("paymentsTableArea"); if (!area) return; if (!rows || !rows.length) { area.innerHTML = `
${t("noPayments")}
`; return; } let html = ``; rows.forEach(function(p){ html += ``; }); area.innerHTML = html + `
${t("orderNumber")}${t("customer")}${t("paymentDate")} ${t("paymentType")}${t("amount")}${t("currency")} ${t("reference")}${t("status")}${t("actions")}
${escapeHtml(p.orderNumber || "")}${escapeHtml(p.customerName || "")} ${escapeHtml(p.paymentDate || "")}${paymentTypeText(p.paymentType)} ${formatQuotationMoney(p.amount || 0)}${escapeHtml(p.currency || "")} ${escapeHtml(p.reference || "")}${paymentStatusText(p.status)}
${ p.status === "Completed" ? `` : "" } ${ hasUiPermission("accounts", "edit") ? `` : "" } ${ p.status !== "Completed" && !hasUiPermission("accounts", "edit") ? "—" : "" }
`; } function filterAccountOrders() { const q = (document.getElementById("accountSearch").value || "").toLowerCase(); drawAccountOrdersTable(accountOrdersData.filter(function(o){ return [o.number,o.customerName,o.currency,o.status].join(" ").toLowerCase().includes(q); })); } function paymentTypeText(v) { const m={"Cash":"cash","Bank Transfer":"bankTransfer","Cheque":"cheque","Card":"cardPayment","Other":"other"}; return t(m[v] || "other"); } function paymentStatusText(v) { const m={"Pending":"pending","Completed":"completed","Cancelled":"cancelled"}; return t(m[v] || "pending"); } function paymentTypeOptions(selected) { return ["Bank Transfer","Cash","Cheque","Card","Other"].map(function(v){ return ``; }).join(""); } function paymentStatusOptions(selected) { return ["Completed","Pending","Cancelled"].map(function(v){ return ``; }).join(""); } function openPaymentModal(orderId) { showPaymentModal(null, orderId || ""); } function editPayment(paymentId) { const token = sessionStorage.getItem("pillar_token"); google.script.run.withSuccessHandler(function(result){ if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") return handleExpiredSession(); showToast(t("paymentDetailsError"), "error"); return; } showPaymentModal(result.payment || null, (result.payment || {}).orderId || ""); }).withFailureHandler(function(e){console.error(e);showToast(t("paymentDetailsError"),"error");}) .getPaymentDetails(token, paymentId); } function showPaymentModal(payment, selectedOrderId) { const isEdit = !!(payment && payment.id); const today = new Date().toISOString().slice(0,10); const orderOptions = accountOrdersData.map(function(o){ return ``; }).join(""); const overlay=document.createElement("div"); overlay.id="paymentModalOverlay"; overlay.className="modal-overlay"; overlay.innerHTML=`

${isEdit?t("editPayment"):t("addPayment")}

`; document.body.appendChild(overlay); if (selectedOrderId) paymentOrderChanged(); } function paymentOrderChanged() { const el=document.getElementById("paymentOrder"); if(!el) return; const o=accountOrdersData.find(function(x){return x.id===el.value;}); document.getElementById("paymentCustomer").value=o?o.customerName:""; document.getElementById("paymentCurrency").value=o?o.currency:""; document.getElementById("paymentOrderTotal").value=o?formatQuotationMoney(o.total):""; document.getElementById("paymentPaid").value=o?formatQuotationMoney(o.paid):""; document.getElementById("paymentBalance").value=o?formatQuotationMoney(o.balance):""; } function savePayment(paymentId) { const orderId=document.getElementById("paymentOrder").value; const amount=Number(document.getElementById("paymentAmount").value||0); if(!orderId){showToast(t("orderRequired"),"error");return;} if(!(amount>0)){showToast(t("amountRequired"),"error");return;} const payload={id:paymentId||"",orderId:orderId,paymentDate:document.getElementById("paymentDate").value, paymentType:document.getElementById("paymentType").value,amount:amount,reference:document.getElementById("paymentReference").value.trim(), status:document.getElementById("paymentStatus").value,notes:document.getElementById("paymentNotes").value.trim()}; const token=sessionStorage.getItem("pillar_token"), button=document.getElementById("savePaymentButton"); button.disabled=true; const runner=google.script.run.withSuccessHandler(function(result){button.disabled=false; if(!result||!result.success){if(result&&result.message==="UNAUTHORIZED")return handleExpiredSession(); if(result&&result.message==="AMOUNT_EXCEEDS_BALANCE")showToast(t("amountExceedsBalance")+" "+formatQuotationMoney(result.balance||0),"error"); else showToast((result&&result.message)||t("systemError"),"error"); return;} closePaymentModal(); loadAccounts(); showToast(paymentId?t("paymentUpdated"):t("paymentAdded"),"success"); }).withFailureHandler(function(e){button.disabled=false;console.error(e);showToast(t("systemError"),"error");}); if(paymentId) runner.updatePayment(token,payload); else runner.addPayment(token,payload); } // ====================================================== // PAYMENT RECEIPT / PDF // ====================================================== function printPaymentReceipt( paymentId ) { if (!paymentId) { showToast( t("receiptLoadError"), "error" ); return; } const printWindow = window.open( "", "_blank" ); if (!printWindow) { showToast( currentLanguage === "ar" ? "يرجى السماح بالنوافذ المنبثقة لفتح إيصال الدفع." : "Please allow pop-ups to open the payment receipt.", "error" ); return; } printWindow.document.open(); printWindow.document.write( ` ${escapeHtml(t("loading"))} ${escapeHtml(t("loading"))} ` ); printWindow.document.close(); const token = sessionStorage.getItem( "pillar_token" ); google.script.run .withSuccessHandler( function(result) { if ( !result || !result.success ) { try { printWindow.close(); } catch (e) {} if ( result && result.message === "UNAUTHORIZED" ) { handleExpiredSession(); return; } if ( result && result.message === "PAYMENT_NOT_COMPLETED" ) { showToast( t("receiptStatusError"), "error" ); return; } if ( result && result.message === "RECEIPT_NOT_ISSUED" ) { showToast( t("receiptNotIssued"), "error" ); return; } showToast( t("receiptLoadError"), "error" ); return; } const html = buildPaymentReceiptHtml( result ); printWindow.document.open(); printWindow.document.write(html); printWindow.document.close(); } ) .withFailureHandler( function(error) { console.error( "Payment receipt error:", error ); try { printWindow.close(); } catch (e) {} showToast( t("receiptLoadError"), "error" ); } ) .getPaymentReceiptData( token, paymentId ); } function buildPaymentReceiptHtml( data ) { data = data || {}; const company = data.company || {}; const customer = data.customer || {}; const payment = data.payment || {}; const receipt = data.receipt || {}; const order = data.order || {}; const isArabic = currentLanguage === "ar"; const direction = isArabic ? "rtl" : "ltr"; const oppositeAlign = isArabic ? "left" : "right"; const companyName = company.name || "PILLAR"; const companyLogo = String( company.logoDataUrl || "" ); const companyStamp = String( company.stampDataUrl || "" ); const authorizedSignatory = String( company.authorizedSignatory || "" ); const signatoryTitle = String( company.signatoryTitle || "" ); const companyAddressParts = [ company.address, company.country ].filter(Boolean); const customerLocation = [ customer.address, customer.city, customer.country ].filter(Boolean); const companyContactLines = [ company.phone ? `${t("phone")}: ${company.phone}` : "", company.email ? `${t("email")}: ${company.email}` : "", company.website || "", company.taxNumber ? `${t("taxNumber")}: ${company.taxNumber}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const customerContactLines = [ customer.contactPerson ? `${t("contactPerson")}: ${customer.contactPerson}` : "", customer.phone ? `${t("phone")}: ${customer.phone}` : "", customer.email ? `${t("email")}: ${customer.email}` : "" ] .filter(Boolean) .map(function(line) { return `
${escapeHtml(line)}
`; }) .join(""); const receiptTitle = `${t("paymentReceiptTitle")} ${receipt.number || payment.id || ""}`; return ` ${escapeHtml(receiptTitle)}
${ companyLogo ? `` : "" }
${escapeHtml(companyName)}
${ companyAddressParts.length ? `
${escapeHtml(companyAddressParts.join(", "))}
` : "" }
${companyContactLines}

${escapeHtml(t("paymentReceiptTitle"))}

${escapeHtml(t("receiptNumber"))}: ${escapeHtml(receipt.number || payment.id || "")}
${escapeHtml(t("paymentDate"))}: ${escapeHtml(payment.paymentDate || "")}
${escapeHtml(t("receivedFrom"))}
${escapeHtml( customer.companyName || payment.customerId || "" )}
${ customerLocation.length ? `
${escapeHtml(customerLocation.join(", "))}
` : "" }
${customerContactLines}
${escapeHtml(t("amountReceived"))}
${escapeHtml( formatQuotationMoney( payment.amount || 0 ) )} ${escapeHtml(payment.currency || "")}
${escapeHtml(t("orderNumber"))} ${escapeHtml(order.number || "")}
${ data.invoiceNumber ? `
${escapeHtml(t("relatedInvoice"))} ${escapeHtml(data.invoiceNumber)}
` : "" }
${escapeHtml(t("paymentType"))} ${escapeHtml(paymentTypeText(payment.paymentType || "Other"))}
${escapeHtml(t("paymentReference"))} ${escapeHtml(payment.reference || "—")}
${escapeHtml(t("status"))} ${escapeHtml(paymentStatusText(payment.status || "Completed"))}
${ payment.notes ? `
${escapeHtml(t("notes"))}
${escapeHtml(payment.notes)}
` : "" } ${ companyStamp || authorizedSignatory || signatoryTitle ? `
${escapeHtml(t("authorizedSignature"))}
${ companyStamp ? `${escapeAttribute(t(` : "" }
${ authorizedSignatory ? `
${escapeHtml(authorizedSignatory)}
` : "" } ${ signatoryTitle ? `
${escapeHtml(signatoryTitle)}
` : "" }
` : "" }
`; } function closePaymentModal(){const m=document.getElementById("paymentModalOverlay");if(m)m.remove();} // ====================================================== // USERS PAGE // ====================================================== function renderUsersPage() { const content = document.getElementById("mainContent"); if (!currentUser || (currentUser.role !== "Super Admin" && currentUser.role !== "Admin")) { content.innerHTML = `

${t("users")}

${t("accessDenied")}

`; return; } content.innerHTML = `
${hasUiPermission("users", "create") ? `` : ""}
${t("loading")}
`; loadUsers(); } function loadUsers() { const token = sessionStorage.getItem("pillar_token"); google.script.run .withSuccessHandler(function(result) { if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") return handleExpiredSession(); if (result && result.message === "ACCESS_DENIED") { document.getElementById("usersTableArea").innerHTML = `
${t("accessDenied")}
`; return; } showToast(t("usersLoadError"), "error"); return; } usersData = result.users || []; drawUsersTable(usersData); }) .withFailureHandler(function(error) { console.error(error); showToast(t("usersLoadError"), "error"); }) .getUsers(token); } function drawUsersTable(rows) { const area = document.getElementById("usersTableArea"); if (!area) return; if (!rows || !rows.length) { area.innerHTML = `
${t("noUsers")}
`; return; } let html = ``; rows.forEach(function(u) { html += ``; }); area.innerHTML = html + `
${t("fullName")} ${t("email")} ${t("role")} ${t("status")} Created ${t("actions")}
${escapeHtml(u.fullName || "")} ${escapeHtml(u.email || "")} ${escapeHtml(userRoleText(u.role))} ${u.status === "Active" ? t("active") : t("inactive")} ${escapeHtml(u.createdAt || "")} ${hasUiPermission("users", "edit") ? ` ` : "—"}
`; } function filterUsers() { const el = document.getElementById("userSearch"); const q = (el ? el.value : "").toLowerCase(); drawUsersTable(usersData.filter(function(u) { return [ u.fullName, u.email, u.role, u.status ].join(" ").toLowerCase().includes(q); })); } function userRoleText(role) { const map = { "Super Admin": "superAdmin", "Admin": "admin", "Sales": "sales", "Operations": "operations", "Accounts": "accountsRole", "Viewer": "viewer" }; return t(map[role] || role); } function userRoleOptions(selected) { return ["Super Admin", "Admin", "Sales", "Operations", "Accounts", "Viewer"] .map(function(role) { return ``; }).join(""); } function userStatusOptions(selected) { return ["Active", "Inactive"].map(function(status) { const label = status === "Active" ? t("active") : t("inactive"); return ``; }).join(""); } function openUserModal() { showUserModal(null); } function editUser(userId) { const user = usersData.find(function(u) { return String(u.id) === String(userId); }); if (!user) { showToast(t("systemError"), "error"); return; } showUserModal(user); } function showUserModal(user) { const isEdit = !!(user && user.id); const overlay = document.createElement("div"); overlay.id = "userModalOverlay"; overlay.className = "modal-overlay"; overlay.innerHTML = `

${isEdit ? t("editUser") : t("addUser")}

${isEdit ? "" : `
`}
`; document.body.appendChild(overlay); } function saveUser(userId) { const fullName = (document.getElementById("userFullName").value || "").trim(); const email = (document.getElementById("userEmail").value || "").trim(); const role = document.getElementById("userRole").value; const status = document.getElementById("userStatus").value; if (!fullName) { showToast(t("fullNameRequired"), "error"); return; } if (!email) { showToast(t("emailRequired"), "error"); return; } const payload = { id: userId || "", fullName: fullName, email: email, role: role, status: status }; if (!userId) { const password = document.getElementById("userPassword").value || ""; const confirmPassword = document.getElementById("userConfirmPassword").value || ""; if (!password) { showToast(t("passwordRequired"), "error"); return; } if (password.length < 6) { showToast(t("passwordTooShort"), "error"); return; } if (password !== confirmPassword) { showToast(t("passwordsDoNotMatch"), "error"); return; } payload.password = password; } const token = sessionStorage.getItem("pillar_token"); const button = document.getElementById("saveUserButton"); button.disabled = true; const runner = google.script.run .withSuccessHandler(function(result) { button.disabled = false; if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") return handleExpiredSession(); showToast(userErrorText(result && result.message), "error"); return; } closeUserModal(); loadUsers(); showToast(userId ? t("userUpdated") : t("userAdded"), "success"); }) .withFailureHandler(function(error) { button.disabled = false; console.error(error); showToast(t("systemError"), "error"); }); if (userId) runner.updateUser(token, payload); else runner.addUser(token, payload); } function openChangePasswordModal(userId) { const user = usersData.find(function(u) { return String(u.id) === String(userId); }); if (!user) { showToast(t("systemError"), "error"); return; } const overlay = document.createElement("div"); overlay.id = "userPasswordModalOverlay"; overlay.className = "modal-overlay"; overlay.innerHTML = `

${t("changePassword")}

${escapeHtml(user.fullName || "")} — ${escapeHtml(user.email || "")}
`; document.body.appendChild(overlay); } function saveUserPassword(userId) { const password = document.getElementById("newUserPassword").value || ""; const confirmPassword = document.getElementById("confirmNewUserPassword").value || ""; if (!password) { showToast(t("passwordRequired"), "error"); return; } if (password.length < 6) { showToast(t("passwordTooShort"), "error"); return; } if (password !== confirmPassword) { showToast(t("passwordsDoNotMatch"), "error"); return; } const token = sessionStorage.getItem("pillar_token"); const button = document.getElementById("changeUserPasswordButton"); button.disabled = true; google.script.run .withSuccessHandler(function(result) { button.disabled = false; if (!result || !result.success) { if (result && result.message === "UNAUTHORIZED") return handleExpiredSession(); showToast(userErrorText(result && result.message), "error"); return; } closeUserPasswordModal(); showToast(t("passwordChanged"), "success"); }) .withFailureHandler(function(error) { button.disabled = false; console.error(error); showToast(t("systemError"), "error"); }) .changeUserPassword(token, userId, password); } function userErrorText(code) { const map = { "ACCESS_DENIED": "accessDenied", "FULL_NAME_REQUIRED": "fullNameRequired", "EMAIL_REQUIRED": "emailRequired", "INVALID_EMAIL": "invalidEmail", "PASSWORD_REQUIRED": "passwordRequired", "PASSWORD_TOO_SHORT": "passwordTooShort", "EMAIL_ALREADY_EXISTS": "emailAlreadyExists", "CANNOT_DISABLE_YOURSELF": "cannotDisableYourself", "CANNOT_CHANGE_OWN_ROLE": "cannotChangeOwnRole", "LAST_SUPER_ADMIN_REQUIRED": "lastSuperAdminRequired" }; return map[code] ? t(map[code]) : (code || t("systemError")); } function closeUserModal() { const modal = document.getElementById("userModalOverlay"); if (modal) modal.remove(); } function closeUserPasswordModal() { const modal = document.getElementById("userPasswordModalOverlay"); if (modal) modal.remove(); } // ====================================================== function handleExpiredSession() { const hasSession = !!sessionStorage.getItem( "pillar_token" ) || !!currentUser; if ( !hasSession || sessionExpiryHandled ) { return; } sessionExpiryHandled = true; alert( t( "sessionExpired" ) ); finishLogout(); } // ====================================================== // LOGOUT // ====================================================== function logout() { const token = sessionStorage.getItem( "pillar_token" ); if (!token) { finishLogout(); return; } google.script.run .withSuccessHandler( function() { finishLogout(); } ) .withFailureHandler( function() { finishLogout(); } ) .logoutUser( token ); } function finishLogout() { sessionStorage.removeItem( "pillar_token" ); sessionStorage.removeItem( "pillar_user" ); currentUser = null; customersData = []; suppliersData = []; rfqsData = []; rfqCustomersData = []; quotationsData = []; quotationRFQsData = []; ordersData = []; orderQuotationsData = []; invoicesData = []; receiptsData = []; accountOrdersData = []; paymentsData = []; usersData = []; settingsData = {}; currentPage = "dashboard"; const mainContent = document.getElementById( "mainContent" ); if (mainContent) { mainContent.innerHTML = ""; } document .getElementById( "appPage" ) .classList .add( "hidden" ); document .getElementById( "loginPage" ) .classList .remove( "hidden" ); document .getElementById( "loginLanguageButton" ) .classList .remove( "hidden" ); document.getElementById( "password" ).value = ""; const loginMessage = document.getElementById( "message" ); if (loginMessage) { loginMessage.textContent = ""; loginMessage.className = "message"; loginMessage.style.display = "none"; } applyLanguage(); } // ====================================================== // SECURITY HELPERS // ====================================================== function escapeHtml( value ) { const div = document.createElement( "div" ); div.textContent = value == null ? "" : value; return div.innerHTML; } function escapeAttribute( value ) { return String( value == null ? "" : value ) .replace( /&/g, "&" ) .replace( /"/g, """ ) .replace( //g, ">" ); } function escapeJs( value ) { return String( value == null ? "" : value ) .replace( /\\/g, "\\\\" ) .replace( /'/g, "\\'" ); } // ====================================================== // START // ====================================================== function restoreExistingSession() { const token = sessionStorage.getItem( "pillar_token" ); if (!token) { sessionStorage.removeItem( "pillar_user" ); return; } google.script.run .withSuccessHandler( function(result) { if ( !result || !result.valid ) { finishLogout(); return; } const user = result.user || { id: result.userId || "", name: result.fullName || "", email: result.email || "", role: result.role || "" }; currentUser = user; sessionStorage.setItem( "pillar_user", JSON.stringify( user ) ); sessionExpiryHandled = false; showApplication(); } ) .withFailureHandler( function(error) { console.error( "Session restore error:", error ); const message = document.getElementById( "message" ); if (message) { message.textContent = t( "systemError" ); message.className = "message error"; } } ) .validateSession( token ); } applyLanguage(); loadDefaultLanguage(); restoreExistingSession(); // ====================================================== // TOAST NOTIFICATIONS // ====================================================== function showToast(message, type) { const oldToast = document.getElementById( "pillarToast" ); if (oldToast) { oldToast.remove(); } const toast = document.createElement( "div" ); toast.id = "pillarToast"; toast.textContent = message; toast.style.position = "fixed"; toast.style.top = "90px"; if ( currentLanguage === "ar" ) { toast.style.left = "30px"; } else { toast.style.right = "30px"; } toast.style.zIndex = "99999"; toast.style.padding = "14px 20px"; toast.style.borderRadius = "8px"; toast.style.fontWeight = "700"; toast.style.boxShadow = "0 4px 18px rgba(0,0,0,.15)"; if ( type === "success" ) { toast.style.background = "#e8f5ed"; toast.style.color = "#146c43"; toast.style.border = "1px solid #badbcc"; } else { toast.style.background = "#fdecec"; toast.style.color = "#b42318"; toast.style.border = "1px solid #f5c2c0"; } document.body.appendChild( toast ); setTimeout( function() { if (toast) { toast.remove(); } }, 3500 ); }